package com.unitd.frame.springweb.upload.multipart;

import com.unitd.frame.comm.utils.CharsetUtils;
import com.unitd.frame.springweb.upload.base.UploadCode;
import com.unitd.frame.springweb.upload.base.UploadFile;
import com.unitd.frame.springweb.upload.base.UploadFileHeader;
import com.unitd.frame.springweb.upload.multipart.part.FilePart;
import com.unitd.frame.springweb.upload.multipart.part.ParamPart;
import com.unitd.frame.springweb.upload.multipart.part.Part;
import com.unitd.frame.springweb.upload.multipart.policy.IFileRenamePolicy;
import com.unitd.frame.springweb.upload.multipart.policy.impl.UploadFileRenamePolicy;
import com.unitd.frame.springweb.upload.multipart.stream.PartOutputStream;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;

/**
 * @desc 前端文件上传请求对象类
 * @filename UploadMultipartRequest.java
 * @copyright www.unitd.com
 * @author Hudan
 * @version 1.0
 * @date 2017/4/10
 */

public class UploadMultipartRequest {

	protected Logger logger = LoggerFactory.getLogger(this.getClass());

	/** 默认的上传的最大文件 */
	private static final int DEFAULT_MAX_POST_SIZE = 1048576;
	protected Hashtable files;
	/** Servlet请求 */
	private HttpServletRequest request;
	/** 保存路径 */
	private String saveDirectory;
	/** 最大上传文件 */
	private int maxPostSize;
	/** 上传文件的头部扩展名 */
	private String fileHeaderExts;
	/** 文件编码格式 */
	private String charset;
	/** 上传文件命名策略 */
	private IFileRenamePolicy fileRenamePolicy;

	protected UploadMultipartRequest() {
		this.files = new Hashtable();
		this.fileHeaderExts = null;
		this.charset = CharsetUtils.UTF_8.name();
	}

	public UploadMultipartRequest(HttpServletRequest request, String saveDirectory) {
		this(request, saveDirectory, 1048576);
	}

	public UploadMultipartRequest(HttpServletRequest request, String saveDirectory, int maxPostSize) {
		this.logger = LoggerFactory.getLogger(UploadMultipartRequest.class);
		this.files = new Hashtable();
		this.fileHeaderExts = null;
		this.charset = CharsetUtils.UTF_8.name();
		this.request = request;
		this.saveDirectory = saveDirectory;
		this.maxPostSize = maxPostSize;
	}

	/**
	 * @desc 处理文件上传
	 */
	public void upload() throws IOException {
		if (this.request == null) {
			throw new IllegalArgumentException("request cannot be null");
		} else if (this.saveDirectory == null) {
			throw new IllegalArgumentException("saveDirectory cannot be null");
		} else if (this.maxPostSize <= 0) {
			throw new IllegalArgumentException("maxPostSize must be positive");
		} else {
			File dir = new File(this.saveDirectory);
			if (!dir.isDirectory()) {
				throw new IllegalArgumentException("Not a directory: " + this.saveDirectory);
			} else if (!dir.canWrite()) {
				throw new IllegalArgumentException("Not writable: " + this.saveDirectory);
			} else {
				MultipartParser parser = new MultipartParser(this.request, this.maxPostSize, true, true, this.getCharset());
				HashMap paramParts = new HashMap();

				Part part;
				while ((part = parser.readNextPart()) != null) {
					String name = part.getName();
					if (name == null) {
						throw new IOException("Malformed input: parameter name missing (known Opera 7 bug)");
					}

					if (part.isFile()) {
						FilePart paramPart = (FilePart) part;
						String fileName = paramPart.getFileName();
						if (fileName != null) {
							UploadFile cfi = this.writeTo(dir, fileName, this.getFileRenamePolicy(), paramPart);
							cfi.setDir(dir.toString());
							cfi.setOriginal(fileName);
							cfi.setParamParts(paramParts);
							this.files.put(name, cfi);
						} else {
							this.files.put(name, new UploadFile());
						}
					} else if (part.isParam()) {
						ParamPart paramPart1 = (ParamPart) part;
						paramParts.put(paramPart1.getName(), paramPart1.getStringValue());
					}
				}
			}
		}
	}

	/**
	 * @desc 处理文件上传操作,将上传文件按照规则写入指定路径下
	 * @param fileOrDirectory 需要上传的文件目录
	 * @param fileName 文件名称
	 * @param policy 上传文件重命名策略
	 * @param filePart 上传文件分片信息
	 * @return 上传后的文件对象
	 * @throws IOException
	 */
	private UploadFile writeTo(File fileOrDirectory, String fileName, IFileRenamePolicy policy, FilePart filePart) throws IOException {
		BufferedOutputStream fileOut = null;
		UploadFile cf = new UploadFile();

		try {
			try {
				if (fileName == null) {
					return cf;
				}

				BufferedInputStream e = new BufferedInputStream(filePart.getInputStream());
				cf.setType(filePart.getContentType());
				if (StringUtils.isNotBlank(this.fileHeaderExts)) {
					try {
						byte[] file = new byte[3];
						e.mark(3);
						e.read(file, 0, file.length);
						e.reset();
						String fileExt1 = this.readFileExt(file, fileName);
						if (fileExt1 == null) {
							cf.setUploadCode(UploadCode.ILLEGAL_EXT);
							this.logger.debug(" upload fileType is null.");
							UploadFile var10 = cf;
							return var10;
						}

						cf.setSuffix(fileExt1);
					} catch (Exception var15) {
						this.logger.debug("upload file error. ", var15);
						cf.setUploadCode(UploadCode.EXCEPTION);
						UploadFile fileExt = cf;
						return fileExt;
					}
				} else {
					cf.setSuffix(fileName.substring(fileName.lastIndexOf(".")));
				}

				File file1;
				if (fileOrDirectory.isDirectory()) {
					file1 = new File(fileOrDirectory, fileName);
				} else {
					file1 = fileOrDirectory;
				}

				if (policy instanceof UploadFileRenamePolicy) {
					((UploadFileRenamePolicy) policy).setSuffix(cf.getSuffix());
				}

				if (policy != null) {
					file1 = policy.rename(file1);
					fileName = file1.getName();
					cf.setFilename(fileName);
				}

				fileOut = new BufferedOutputStream(new FileOutputStream(file1));
				cf.setSize(this.write(fileOut, filePart.getContentType(), e));
			} catch (Exception var16) {
				this.logger.debug("upload file write error. ", var16);
				cf.setUploadCode(UploadCode.EXCEPTION);
			}
			return cf;
		} finally {
			if (fileOut != null) {
				fileOut.close();
			}
		}
	}

	/**
	 * @desc 获取上传文件的后缀名
	 * @param data 文件数据内容
	 * @param fileName 文件名称
	 * @return 文件后缀名
	 */
	private String readFileExt(byte[] data, String fileName) {
		String fileExt = fileName.substring(fileName.lastIndexOf("."));
		StringBuffer fe = new StringBuffer();
		fe.append(UploadFileHeader.bytesToHexString(data));
		fe.append(fileExt);
		return this.fileHeaderExts.contains(fe.toString()) ? fileExt : null;
	}

	private long write(OutputStream out, String contentType, InputStream in) throws IOException {
		if (contentType.equals("application/x-macbinary")) {
			out = new PartOutputStream((OutputStream) out);
		}

		long size = 0L;
		int read;
		for (byte[] buf = new byte[8192]; (read = in.read(buf)) != -1; size += (long) read) {
			((OutputStream) out).write(buf, 0, read);
		}

		return size;
	}

	public UploadMultipartRequest(ServletRequest request, String saveDirectory) throws IOException {
		this((HttpServletRequest) request, saveDirectory);
	}

	public UploadMultipartRequest(ServletRequest request, String saveDirectory, int maxPostSize) throws IOException {
		this((HttpServletRequest) request, saveDirectory, maxPostSize);
	}

	public Enumeration getFileNames() {
		return this.files.keys();
	}

	public UploadFile getUploadFile(String name) {
		try {
			return (UploadFile) this.files.get(name);
		} catch (Exception var3) {
			return null;
		}
	}

	public HttpServletRequest getRequest() {
		return this.request;
	}

	public String getSaveDirectory() {
		return this.saveDirectory;
	}

	public IFileRenamePolicy getFileRenamePolicy() {
		return (IFileRenamePolicy) (this.fileRenamePolicy == null ? new UploadFileRenamePolicy() : this.fileRenamePolicy);
	}

	public void setFileRenamePolicy(IFileRenamePolicy fileRenamePolicy) {
		this.fileRenamePolicy = fileRenamePolicy;
	}

	public String getFileHeaderExts() {
		return this.fileHeaderExts;
	}

	public void setFileHeaderExts(String fileHeaderExts) {
		this.fileHeaderExts = fileHeaderExts;
	}

	public String getCharset() {
		return this.charset;
	}

	public void setCharset(String charset) {
		this.charset = charset;
	}
}